package org.fing.edu.uy.esbadp.modules.admAdaptation;

import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.stream.XMLStreamException;

import org.fing.edu.uy.esbadp.constants.ExceptionConstants;
import org.fing.edu.uy.esbadp.constants.SoapConstants;
import org.fing.edu.uy.esbadp.core.AdpFlowInterface;
import org.fing.edu.uy.esbadp.core.AdpFlowSYNC;
import org.fing.edu.uy.esbadp.core.AdpFlowTree;
import org.fing.edu.uy.esbadp.core.entity.DMRegisteredService;
import org.fing.edu.uy.esbadp.core.entity.GenericTreeNode;
import org.fing.edu.uy.esbadp.logger.LoggerADP;
import org.fing.edu.uy.esbadp.modules.persistance.ServiceManagerFactory;
import org.fing.edu.uy.esbadp.utils.SoapExtraction;
import org.jboss.soa.esb.message.Message;

public class AdpAdministrator {

	private static final java.util.logging.Logger logger = LoggerADP.getLogger(AdpAdministrator.class.getSimpleName());
	
	/* instancia */
	private static AdpAdministrator instance = null;

	/*Map debe ahora ser sincronizado porque se accede desde el hilo del motor de Adaptacion*/
	private ConcurrentHashMap<String, AdpFlowTree> hash;
	
	/* propiedades */
	private final static String REGEX_CONTENT = "<.*>(.*)</.*>";

	private AdpAdministrator() {
		this.hash = new ConcurrentHashMap<String, AdpFlowTree>();
		
		try{
			 List<DMRegisteredService> services = ServiceManagerFactory.getServiceManager().getAllVirtualServices();
			 for(DMRegisteredService service : services){
				 getAdpFlow(service.getEndPointURL());
			 }
			 logger.info("Has successfully loaded default strategies...");
		 }catch (Exception ex) {
			 logger.log(Level.SEVERE, "Error when load default strategies. See Trace.", ex);
		 }
	}

	public static synchronized AdpAdministrator getInstance() {
		if (instance == null) {
			instance = new AdpAdministrator();
		}
		return instance;
	}

	/*
	 * A partir de la url del endpoint que se quiere invokar se obtendra la
	 * directiva de adaptacion definida en el hash.
	 * 
	 * Si no hay directiva en el hash se accede a la base y se busca el servicio
	 * virtual definido.
	 * 
	 * Si no existe un servicio definido se retorna un error.
	 * 
	 * @Throw NotVirtualServiceFound
	 */
	public AdpFlowTree getAdaptationFlowTree(Message message) throws Exception {
		String endPointurl = getEndpointUrl(message);
		if(endPointurl == null || endPointurl.matches("\\s*")){
			//No respeta el standard WS-Addressing
			throw new Exception(ExceptionConstants.EXCEPTION_INAVLID_HEADER_WSA);
		}else{
			AdpFlowTree adpTree = hash.get(endPointurl);
			if (adpTree == null) {
				//No existe un servicio registrado para dicho endpoint
				throw new Exception(ExceptionConstants.EXCEPTION_INAVLID_SERVICE + endPointurl);
			} else {
				AdpFlowTree partialClone = (AdpFlowTree) adpTree.clone();
				return partialClone;
			}
		}
	}

	/* Agrega un nuevo elemento de adaptacion para el endpoint */
	
	/**
	 * 
	 * @param endpoint endpoint del servicio que se va a adaptar
	 * @param adp AdpFlow que representa la adaptacion
	 */
	public void addAdpFlow(String endpoint, AdpFlowTree adp) {				
		this.hash.put(endpoint.toLowerCase(), adp);
	}
	
	/**
	 * 
	 * @param endpoint key del hash de arboles de adaptacion
	 * @return la adaptacion para el endpoint recibido , si no existe ser retornala la adaptacion generica la cual
	 * consta de una llamada directa al servicio virtual y la agrega para la proxima llamada.
	 * 
	 * puede llegar a retornar null si el endpoint no esta registrado.
	 * 
	 */
	public AdpFlowTree getAdpFlow(String endpoint){
		String endpointAux = endpoint.toLowerCase();
		//si existe y esta activa.		
		if(this.hash.containsKey(endpointAux) && this.hash.get(endpointAux).isActiveADP()){		
			return this.hash.get(endpointAux);
		}else{
			logger.log(Level.WARNING, "There are not active AdpFlow for:" + endpoint);
			//genero la adaptacion generica y la agrego al hash para la proxima consulta.
			AdpFlowTree defRoot = generateDefaultADP(endpointAux);
			if(defRoot!=null){
				this.hash.put(endpointAux, defRoot);
			}		
			return defRoot;
		}
	}
	
	/********************************* FUNCIONES PRIVADAS DE SOPORTE **************************************/
	/******************************************************************************************************/
	
	/***
	 * 
	 * @param endpoint correspondiente a un servicio virtual registrado
	 * @return un flow con una unica llamada al servicio virtual registrado para ese endpoint
	 * 		   si no existe registro para el endpoint se retorna null.
	 */
	private AdpFlowTree generateDefaultADP(String endpoint) {
		try {
			DMRegisteredService serv = 	ServiceManagerFactory.getServiceManager().getVirtualService(endpoint);
			
			if(serv!=null){
				// insert de default virtual service
				GenericTreeNode<AdpFlowInterface> treeNode = new GenericTreeNode<AdpFlowInterface>();
				AdpFlowInterface action = new AdpFlowSYNC(serv.getVirtualServiceName(),serv.getVirtualServiceCategory(), false, 0, 0, serv.getServiceID());
				treeNode.setData(action);
				
				AdpFlowTree flow = new AdpFlowTree(serv.getServiceID(),treeNode);
				flow.setDefaultADP(true);		
			
				return flow;				
			}else{
				return null;
			}			
		} catch (SQLException e) {
			logger.log(Level.SEVERE, "Error getting virtual service info for endpoint =" + endpoint, e);
			return null;
		}
	}

	/**
	 * @param message
	 * @return urlString del endpoint definido en la propiedad To del header del mensaje.
	 * @throws XMLStreamException 
	 */
	private String getEndpointUrl(Message message) throws XMLStreamException {
		String serviveEndpoint = SoapExtraction.findElementInBody(message, SoapConstants.SOAP_WSA_TO_QNAME);
		if(serviveEndpoint != null){
			Pattern patron = Pattern.compile(REGEX_CONTENT);
			Matcher matcher = patron.matcher(serviveEndpoint);
			if (matcher.find()) {
				serviveEndpoint = matcher.group(1);
			}
		}
		return serviveEndpoint.toLowerCase();
	}

}
